/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "common.h"

// implementation header
#include "MeshTransform.h"

// system headers
#include <math.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <glm/mat4x4.hpp>
#include <glm/gtc/type_ptr.hpp>

// common headers
#include "Pack.h"


//
// Mesh Transform Manager
//

MeshTransformManager TRANSFORMMGR;


MeshTransformManager::MeshTransformManager()
{
    return;
}


MeshTransformManager::~MeshTransformManager()
{
    clear();
    return;
}


void MeshTransformManager::clear()
{
    std::vector<MeshTransform*>::iterator it;
    for (it = transforms.begin(); it != transforms.end(); ++it)
        delete *it;
    transforms.clear();
    return;
}


void MeshTransformManager::update()
{
    return;
}


int MeshTransformManager::addTransform(MeshTransform* transform)
{
    transforms.push_back (transform);
    return ((int)transforms.size() - 1);
}


int MeshTransformManager::findTransform(const std::string& transform) const
{
    if (transform.size() <= 0)
        return -1;
    else if ((transform[0] >= '0') && (transform[0] <= '9'))
    {
        int index = atoi (transform.c_str());
        if ((index < 0) || (index >= (int)transforms.size()))
            return -1;
        else
            return index;
    }
    else
    {
        for (int i = 0; i < (int)transforms.size(); i++)
        {
            if (transforms[i]->getName() == transform)
                return i;
        }
        return -1;
    }
}


void * MeshTransformManager::pack(void *buf) const
{
    std::vector<MeshTransform*>::const_iterator it;
    buf = nboPackUInt(buf, (int)transforms.size());
    for (it = transforms.begin(); it != transforms.end(); ++it)
    {
        MeshTransform* transform = *it;
        buf = transform->pack(buf);
    }
    return buf;
}


const void * MeshTransformManager::unpack(const void *buf)
{
    unsigned int i;
    uint32_t count;
    buf = nboUnpackUInt (buf, count);
    for (i = 0; i < count; i++)
    {
        MeshTransform* transform = new MeshTransform;
        buf = transform->unpack(buf);
        addTransform(transform);
    }
    return buf;
}


int MeshTransformManager::packSize() const
{
    int fullSize = sizeof (uint32_t);
    std::vector<MeshTransform*>::const_iterator it;
    for (it = transforms.begin(); it != transforms.end(); ++it)
    {
        MeshTransform* transform = *it;
        fullSize = fullSize + transform->packSize();
    }
    return fullSize;
}


void MeshTransformManager::print(std::ostream& out, const std::string& indent) const
{
    std::vector<MeshTransform*>::const_iterator it;
    for (it = transforms.begin(); it != transforms.end(); ++it)
    {
        MeshTransform* transform = *it;
        transform->print(out, indent);
    }
}


//
// Mesh Transform Tool
//

static void multiply(float m[4][4], const float n[4][4])
{
    float t[4][4];
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            t[i][j] = (m[0][j] * n[i][0]) + (m[1][j] * n[i][1]) +
                      (m[2][j] * n[i][2]) + (m[3][j] * n[i][3]);
        }
    }
    memcpy (m, t, sizeof(float[4][4]));
    return;
}


static void shift(float m[4][4], const float p[3])
{
    const float t[4][4] = {{1.0f, 0.0f, 0.0f, p[0]},
        {0.0f, 1.0f, 0.0f, p[1]},
        {0.0f, 0.0f, 1.0f, p[2]},
        {0.0f, 0.0f, 0.0f, 1.0f}
    };
    multiply(m, t);
    return;
}


static void scale(float m[4][4], const float p[3])
{
    const float t[4][4] = {{p[0], 0.0f, 0.0f, 0.0f},
        {0.0f, p[1], 0.0f, 0.0f},
        {0.0f, 0.0f, p[2], 0.0f},
        {0.0f, 0.0f, 0.0f, 1.0f}
    };
    multiply(m, t);
    return;
}


static void shear(float m[4][4], const float p[3])
{
    const float t[4][4] = {{1.0f, 0.0f, p[0], 0.0f},
        {0.0f, 1.0f, p[1], 0.0f},
        {p[2], 0.0f, 1.0f, 0.0f},
        {0.0f, 0.0f, 0.0f, 1.0f}
    };
    multiply(m, t);
    return;
}


static void spin(float m[4][4], const float radians, const float normal[3])
{
    // normalize
    const float len = (normal[0] * normal[0]) +
                      (normal[1] * normal[1]) +
                      (normal[2] * normal[2]);
    if (len <= 0.0f)
        return;
    const float scale = 1.0f / sqrtf(len);
    const float n[3] = {normal[0] * scale,
                        normal[1] * scale,
                        normal[2] * scale
                       };

    // setup
    const float cos_val = cosf(radians);
    const float sin_val = sinf(radians);
    const float icos_val = (1.0f - cos_val);
    float t[4][4];
    t[3][3] = 1.0f;
    t[0][3] = t[1][3] = t[2][3] = 0.0f;
    t[3][0] = t[3][1] = t[3][2] = 0.0f;
    t[0][0] = (n[0] * n[0] * icos_val) + cos_val;
    t[0][1] = (n[0] * n[1] * icos_val) - (n[2] * sin_val);
    t[0][2] = (n[0] * n[2] * icos_val) + (n[1] * sin_val);
    t[1][0] = (n[1] * n[0] * icos_val) + (n[2] * sin_val);
    t[1][1] = (n[1] * n[1] * icos_val) + cos_val;
    t[1][2] = (n[1] * n[2] * icos_val) - (n[0] * sin_val);
    t[2][0] = (n[2] * n[0] * icos_val) - (n[1] * sin_val);
    t[2][1] = (n[2] * n[1] * icos_val) + (n[0] * sin_val);
    t[2][2] = (n[2] * n[2] * icos_val) + cos_val;

    // execute
    multiply(m, t);

    return;
}


MeshTransform::Tool::Tool(const MeshTransform& xform)
{
    // load the identity matrices
    int i, j;
    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 4; j++)
        {
            if (i == j)
                vertexMatrix[i][j] = 1.0f;
            else
                vertexMatrix[i][j] = 0.0f;
        }
    }
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            if (i == j)
                normalMatrix[i][j] = 1.0f;
            else
                normalMatrix[i][j] = 0.0f;
        }
    }

    skewed = false;
    if (xform.transforms.size() > 0)
        empty = false;
    else
    {
        empty = true;
        inverted = false;
        return;
    }

    // setup the matrices
    processTransforms(xform.transforms);

    // generate the normal matrix
    const float (*vm)[4] = vertexMatrix;
    normalMatrix[0][0] = (vm[1][1] * vm[2][2]) - (vm[1][2] * vm[2][1]);
    normalMatrix[0][1] = (vm[1][2] * vm[2][0]) - (vm[1][0] * vm[2][2]);
    normalMatrix[0][2] = (vm[1][0] * vm[2][1]) - (vm[1][1] * vm[2][0]);
    normalMatrix[1][0] = (vm[2][1] * vm[0][2]) - (vm[2][2] * vm[0][1]);
    normalMatrix[1][1] = (vm[2][2] * vm[0][0]) - (vm[2][0] * vm[0][2]);
    normalMatrix[1][2] = (vm[2][0] * vm[0][1]) - (vm[2][1] * vm[0][0]);
    normalMatrix[2][0] = (vm[0][1] * vm[1][2]) - (vm[0][2] * vm[1][1]);
    normalMatrix[2][1] = (vm[0][2] * vm[1][0]) - (vm[0][0] * vm[1][2]);
    normalMatrix[2][2] = (vm[0][0] * vm[1][1]) - (vm[0][1] * vm[1][0]);

    // setup the polarity
    const float determinant =
        (vm[0][0] * ((vm[1][1] * vm[2][2]) - (vm[1][2] * vm[2][1]))) +
        (vm[0][1] * ((vm[1][2] * vm[2][0]) - (vm[1][0] * vm[2][2]))) +
        (vm[0][2] * ((vm[1][0] * vm[2][1]) - (vm[1][1] * vm[2][0])));
    if (determinant < 0.0f)
        inverted = true;
    else
        inverted = false;

    return;
}


MeshTransform::Tool::~Tool()
{
    return;
}


void MeshTransform::Tool::processTransforms(
    const std::vector<TransformData>& tforms)
{
    for (unsigned int i = 0; i < tforms.size(); i++)
    {
        const TransformData& transform = tforms[i];
        switch (transform.type)
        {
        case ShiftTransform:
        {
            shift(vertexMatrix, transform.data);
            break;
        }
        case ScaleTransform:
        {
            skewed = true;
            scale(vertexMatrix, transform.data);
            break;
        }
        case ShearTransform:
        {
            skewed = true;
            shear(vertexMatrix, transform.data);
            break;
        }
        case SpinTransform:
        {
            spin(vertexMatrix, transform.data[3], transform.data);
            break;
        }
        case IndexTransform:
        {
            const MeshTransform* xform =
                TRANSFORMMGR.getTransform(transform.index);
            if (xform != NULL)
                processTransforms(xform->transforms);
            break;
        }
        default:
        {
            printf("MeshTransform::Tool(): unknown type: %i\n",
                   transform.type);
            break;
        }
        }
    }

    return;
}


void MeshTransform::Tool::modifyVertex(float v[3]) const
{
    if (empty)
        return;

    float t[3];
    const float (*vm)[4] = vertexMatrix;
    t[0] = (v[0] * vm[0][0]) + (v[1] * vm[0][1]) + (v[2] * vm[0][2]) + vm[0][3];
    t[1] = (v[0] * vm[1][0]) + (v[1] * vm[1][1]) + (v[2] * vm[1][2]) + vm[1][3];
    t[2] = (v[0] * vm[2][0]) + (v[1] * vm[2][1]) + (v[2] * vm[2][2]) + vm[2][3];
    memcpy(v, t, sizeof(float[3]));
}


void MeshTransform::Tool::modifyVertex(glm::vec3 &v) const
{
    if (empty)
        return;

    glm::vec4 t = glm::vec4(v, 1.0f);
    const glm::mat4 vm(glm::make_mat4(vertexMatrix[0]));
    v = vm * t;
}


void MeshTransform::Tool::modifyNormal(float n[3]) const
{
    if (empty)
        return;

    float t[3];
    const float (*nm)[3] = normalMatrix;
    t[0] = (n[0] * nm[0][0]) + (n[1] * nm[0][1]) + (n[2] * nm[0][2]);
    t[1] = (n[0] * nm[1][0]) + (n[1] * nm[1][1]) + (n[2] * nm[1][2]);
    t[2] = (n[0] * nm[2][0]) + (n[1] * nm[2][1]) + (n[2] * nm[2][2]);
    // normalize
    const float len = (t[0] * t[0]) + (t[1] * t[1]) + (t[2] * t[2]);
    if (len > 0.0f)
    {
        const float scale = 1.0f / sqrtf(len);
        n[0] = t[0] * scale;
        n[1] = t[1] * scale;
        n[2] = t[2] * scale;
    }
    else
    {
        n[0] = n[1] = 0.0f; // dunno, going with Z...
        n[2] = 1.0f;
    }

    if (inverted)
    {
        n[0] = -n[0];
        n[1] = -n[1];
        n[2] = -n[2];
    }

    return;
}


void MeshTransform::Tool::modifyOldStyle(float pos[3], float size[3],
        float& angle, bool& flipz) const
{
    if (empty)
    {
        flipz = false;
        return;
    }

    // straight transform
    modifyVertex(pos);

    // transform the object's axis unit vectors
    const float cos_val = cosf (angle);
    const float sin_val = sinf (angle);
    float x[3], y[3], z[3];
    const float (*vm)[4] = vertexMatrix;
    // NOTE - the translation (shift) elements are not used
    x[0] = (+cos_val * vm[0][0]) + (+sin_val * vm[0][1]);
    x[1] = (+cos_val * vm[1][0]) + (+sin_val * vm[1][1]);
    x[2] = (+cos_val * vm[2][0]) + (+sin_val * vm[2][1]);
    y[0] = (-sin_val * vm[0][0]) + (+cos_val * vm[0][1]);
    y[1] = (-sin_val * vm[1][0]) + (+cos_val * vm[1][1]);
    y[2] = (-sin_val * vm[2][0]) + (+cos_val * vm[2][1]);
    z[0] = vm[0][2];
    z[1] = vm[1][2];
    z[2] = vm[2][2];
    const float xlen = sqrtf ((x[0] * x[0]) + (x[1] * x[1]) + (x[2] * x[2]));
    const float ylen = sqrtf ((y[0] * y[0]) + (y[1] * y[1]) + (y[2] * y[2]));
    const float zlen = sqrtf ((z[0] * z[0]) + (z[1] * z[1]) + (z[2] * z[2]));
    size[0] *= xlen;
    size[1] *= ylen;
    size[2] *= zlen;

    // setup the angle
    angle = atan2f (x[1], x[0]);

    // see if the Z axis has flipped
    if (z[2] < 0.0f)
    {
        flipz = true;
        pos[2] = pos[2] - size[2];
    }
    else
        flipz = false;

    return;
}


//
// Mesh Transform
//

MeshTransform::MeshTransform()
{
    name = "";
    transforms.clear();

    return;
}


MeshTransform::MeshTransform(const MeshTransform& old)
{
    for (unsigned int i = 0; i < old.transforms.size(); i++)
        transforms.push_back(old.transforms[i]);
}


MeshTransform::~MeshTransform()
{
    return;
}


MeshTransform& MeshTransform::operator=(const MeshTransform& old)
{
    name = ""; // not copied
    transforms.clear();
    for (unsigned int i = 0; i < old.transforms.size(); i++)
        transforms.push_back(old.transforms[i]);
    return *this;
}


void MeshTransform::append(const MeshTransform& xform)
{
    for (unsigned int i = 0; i < xform.transforms.size(); i++)
        transforms.push_back(xform.transforms[i]);
    return;
}


void MeshTransform::prepend(const MeshTransform& xform)
{
    if (xform.transforms.size() <= 0)
        return;
    MeshTransform oldCopy = *this;
    transforms.clear();
    unsigned int i;
    for (i = 0; i < xform.transforms.size(); i++)
        transforms.push_back(xform.transforms[i]);
    for (i = 0; i < oldCopy.transforms.size(); i++)
        transforms.push_back(oldCopy.transforms[i]);
    return;
}


void MeshTransform::finalize()
{
    return;
}


bool MeshTransform::setName(const std::string& xformname)
{
    if (xformname.size() <= 0)
    {
        name = "";
        return false;
    }
    else if ((xformname[0] >= '0') && (xformname[0] <= '9'))
    {
        name = "";
        return false;
    }
    else
        name = xformname;
    return true;
}


const std::string& MeshTransform::getName() const
{
    return name;
}


void MeshTransform::addShift(const float shift[3])
{
    TransformData transform;
    memcpy(transform.data, shift, sizeof(float[3]));
    transform.data[3] = 0.0f;
    transform.type = ShiftTransform;
    transform.index = -1;
    transforms.push_back(transform);
    return;
}


void MeshTransform::addScale(const float scale[3])
{
    TransformData transform;
    memcpy(transform.data, scale, sizeof(float[3]));
    transform.data[3] = 0.0f;
    transform.type = ScaleTransform;
    transform.index = -1;
    transforms.push_back(transform);
    return;
}


void MeshTransform::addShear(const float shear[3])
{
    TransformData transform;
    memcpy(transform.data, shear, sizeof(float[3]));
    transform.data[3] = 0.0f;
    transform.type = ShearTransform;
    transform.index = -1;
    transforms.push_back(transform);
    return;
}


void MeshTransform::addSpin(const float degrees, const float normal[3])
{
    const float radians = degrees * DEG2RADf;
    TransformData transform;
    memcpy(transform.data, normal, sizeof(float[3]));
    transform.data[3] = radians;
    transform.type = SpinTransform;
    transform.index = -1;
    transforms.push_back(transform);
    return;
}


void MeshTransform::addReference(int index)
{
    TransformData transform;
    transform.type = IndexTransform;
    transform.index = index;
    transforms.push_back(transform);
    return;
}


void * MeshTransform::pack(void *buf) const
{
    buf = nboPackStdString(buf, name);

    buf = nboPackUInt(buf, (uint32_t)transforms.size());

    for (unsigned int i = 0; i < transforms.size(); i++)
    {
        const TransformData& transform = transforms[i];
        buf = nboPackUByte (buf, (uint8_t) transform.type);
        if (transform.type == IndexTransform)
            buf = nboPackInt (buf, transform.index);
        else
        {
            buf = nboPackVector (buf, transform.data);
            if (transform.type == SpinTransform)
                buf = nboPackFloat (buf, transform.data[3]);
        }
    }

    return buf;
}


const void * MeshTransform::unpack(const void *buf)
{
    buf = nboUnpackStdString(buf, name);

    uint32_t count;
    int32_t inTmp;
    buf = nboUnpackUInt(buf, count);

    for (unsigned int i = 0; i < count; i++)
    {
        TransformData transform;
        uint8_t type;
        buf = nboUnpackUByte (buf, type);
        transform.type = (TransformType) type;
        if (transform.type == IndexTransform)
        {
            buf = nboUnpackInt (buf, inTmp);
            transform.index = int(inTmp);
            float* d = transform.data;
            d[0] = d[1] = d[2] = d[3] = 0.0f;
        }
        else
        {
            transform.index = -1;
            buf = nboUnpackVector (buf, transform.data);
            if (transform.type == SpinTransform)
                buf = nboUnpackFloat (buf, transform.data[3]);
            else
                transform.data[3] = 0.0f;
        }
        transforms.push_back(transform);
    }

    finalize();

    return buf;
}


int MeshTransform::packSize() const
{
    int fullSize = nboStdStringPackSize(name);
    fullSize += sizeof(uint32_t);

    for (unsigned int i = 0; i < transforms.size(); i++)
    {
        const TransformData& transform = transforms[i];
        fullSize += sizeof(uint8_t);
        if (transform.type == IndexTransform)
            fullSize += sizeof(int32_t);
        else
        {
            fullSize += sizeof(float[3]);
            if (transform.type == SpinTransform)
                fullSize += sizeof(float);
        }
    }

    return fullSize;
}


void MeshTransform::print(std::ostream& out, const std::string& indent) const
{
    out << indent << "transform" << std::endl;

    if (name.size() > 0)
        out << indent << "  name " << name << std::endl;

    printTransforms(out, indent);

    out << indent << "end" << std::endl << std::endl;

    return;
}


void MeshTransform::printTransforms(std::ostream& out,
                                    const std::string& indent) const
{
    for (unsigned int i = 0; i < transforms.size(); i++)
    {
        const TransformData& transform = transforms[i];
        const float* d = transform.data;
        switch (transform.type)
        {
        case ShiftTransform:
        {
            out << indent << "  shift "
                << d[0] << " " << d[1] << " " << d[2] << std::endl;
            break;
        }
        case ScaleTransform:
        {
            out << indent << "  scale "
                << d[0] << " " << d[1] << " " << d[2] << std::endl;
            break;
        }
        case ShearTransform:
        {
            out << indent << "  shear "
                << d[0] << " " << d[1] << " " << d[2] << std::endl;
            break;
        }
        case SpinTransform:
        {
            const float degrees = d[3] * RAD2DEGf;
            out << indent << "  spin " << degrees << " "
                << d[0] << " " << d[1] << " " << d[2] << std::endl;
            break;
        }
        case IndexTransform:
        {
            const MeshTransform* xform = TRANSFORMMGR.getTransform(transform.index);
            if (xform != NULL)
            {
                out << indent << "  xform ";
                if (xform->getName().size() > 0)
                    out << xform->getName();
                else
                    out << transform.index;
                out << std::endl;
            }
            break;
        }
        default:
        {
            break;
        }
        }
    }
    return;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
